package com.ccx.demo.business.common.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectResult;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectInputStream;
import com.ccx.demo.business.common.vo.FileInfo;
import com.ccx.demo.config.MinioConfiguration;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import com.utils.enums.Code;
import com.utils.util.FPath;
import lombok.Cleanup;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.channels.SeekableByteChannel;
import java.util.List;
import java.util.Optional;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * Minio 读写服务接口
 *
 * @author 谢长春 2020-11-19
 */
@Primary
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnProperty(value = "spring.app.files.type", havingValue = "minio")
public class MinioFileService implements IFileService {
    private final MinioConfiguration minioConfiguration;
    private final AmazonS3 minioClient;

//    @SneakyThrows
//    private void putObject(final String path, final InputStream inputStream, final boolean replace) {
//        if (log.isInfoEnabled()) {
//            log.info("上传文件路径【{}】：{}{}", fileInfo.getName(), minioConfiguration.getServer(), fileInfo.getPath());
//        }
//        Code.A00001.assertNonBlank(path, "必须指定存储路径");
//        if (!replace) {
//            final boolean fileExists = minioClient.doesObjectExist(minioConfiguration.getBucketName(), path);
//            if (fileExists) { // 文件已存在，不重复上传文件
//                return;
//            }
//        }
//        minioClient.putObject(minioConfiguration.getBucketName(), path, inputStream)
//        // 上传文件到存储桶
//        final ObjectWriteResponse response = minioClient.putObject(PutObjectArgs.builder()
//                .bucket(minioConfiguration.getBucketName())
//                .object(path)
//                .stream(inputStream, inputStream.available(), -1)
//                .build()
//        );
//        if (log.isDebugEnabled()) {
//            log.info("bucket={}, region={}, object={}, etag={}, versionId={}"
//                    , response.bucket()
//                    , response.region()
//                    , response.object()
//                    , response.etag()
//                    , response.versionId()
//            );
//        }
//    }

    @SneakyThrows
    @Override
    public <T extends FileInfo> T write(final MultipartFile uploadFile, final T fileInfo, final boolean replace) {
        if (log.isInfoEnabled()) {
            log.info("上传文件路径【{}】：{}{}", fileInfo.getName(), minioConfiguration.getServer(), fileInfo.getPath());
        }
        Code.A00001.assertNonBlank(fileInfo.getPath(), "必须指定存储路径");
        if (!replace) {
            final boolean fileExists = minioClient.doesObjectExist(minioConfiguration.getBucketName(), fileInfo.getPath());
            if (fileExists) { // 文件已存在，不重复上传文件
                return fileInfo;
            }
        }
        @Cleanup final InputStream inputStream = uploadFile.getInputStream();
        final ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentType(uploadFile.getContentType());
        metadata.setContentLength(inputStream.available());
        // 上传文件到存储桶
        final PutObjectResult result = minioClient.putObject(
                minioConfiguration.getBucketName(),
                fileInfo.getPath(),
                inputStream,
                metadata
        );
        if (log.isDebugEnabled()) {
            log.info("versionId={}, etag={}, contentMd5={}, metadata={}"
                    , result.getVersionId()
                    , result.getETag()
                    , result.getContentMd5()
                    , JSON.toJSONString(result.getMetadata())
            );
        }
        return fileInfo;
    }

    @SneakyThrows
    @Override
    public <T extends FileInfo> T write(final byte[] bytes, final T fileInfo, final boolean replace) {
        @Cleanup final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
//        fileInfo.setName(fileName.getName());
//        byteArrayInputStream.mark(0);
//        fileInfo.setUname(fileName.toMd5Uname(byteArrayInputStream));
//        byteArrayInputStream.reset();
//        putObject(fileInfo.getPath(), byteArrayInputStream, replace);
        if (log.isInfoEnabled()) {
            log.info("上传文件路径【{}】：{}{}", fileInfo.getName(), minioConfiguration.getServer(), fileInfo.getPath());
        }
        Code.A00001.assertNonBlank(fileInfo.getPath(), "必须指定存储路径");
        if (!replace) {
            final boolean fileExists = minioClient.doesObjectExist(minioConfiguration.getBucketName(), fileInfo.getPath());
            if (fileExists) { // 文件已存在，不重复上传文件
                return fileInfo;
            }
        }
        final ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentLength(byteArrayInputStream.available());
        metadata.setContentType(fileInfo.getContentType());
        // 上传文件到存储桶
        final PutObjectResult result = minioClient.putObject(
                minioConfiguration.getBucketName(),
                fileInfo.getPath(),
                byteArrayInputStream,
                metadata
        );
        if (log.isDebugEnabled()) {
            log.info("versionId={}, etag={}, contentMd5={}, metadata={}"
                    , result.getVersionId()
                    , result.getETag()
                    , result.getContentMd5()
                    , JSON.toJSONString(result.getMetadata())
            );
        }
        return fileInfo;
    }

    @SneakyThrows
    @Override
    public <T extends FileInfo> T write(final File file, final T fileInfo, final boolean replace) {
        if (log.isInfoEnabled()) {
            log.info("上传文件路径【{}】：{}{}", fileInfo.getName(), minioConfiguration.getServer(), fileInfo.getPath());
        }
        Code.A00001.assertNonBlank(fileInfo.getPath(), "必须指定存储路径");
        if (!replace) {
            final boolean fileExists = minioClient.doesObjectExist(minioConfiguration.getBucketName(), fileInfo.getPath());
            if (fileExists) { // 文件已存在，不重复上传文件
                return fileInfo;
            }
        }
        @Cleanup final FileInputStream fileInputStream = new FileInputStream(file);
        @Cleanup final BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        final ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentLength(bufferedInputStream.available());
        metadata.setContentType(fileInfo.getContentType());
        // 上传文件到存储桶
        final PutObjectResult result = minioClient.putObject(
                minioConfiguration.getBucketName(),
                fileInfo.getPath(),
                bufferedInputStream,
                metadata
        );
        if (log.isDebugEnabled()) {
            log.info("versionId={}, etag={}, contentMd5={}, metadata={}"
                    , result.getVersionId()
                    , result.getETag()
                    , result.getContentMd5()
                    , JSON.toJSONString(result.getMetadata())
            );
        }
        return fileInfo;
    }
//
//    @SneakyThrows
//    @Override
//    public <T extends FileInfo> T write(final BufferedInputStream bufferedInputStream, final T fileInfo, final boolean replace) {
//        if (log.isInfoEnabled()) {
//            log.info("上传文件路径【{}】：{}{}", fileInfo.getName(), minioConfiguration.getServer(), fileInfo.getPath());
//        }
//        Code.A00001.assertNonBlank(fileInfo.getPath(), "必须指定存储路径");
//        if (!replace) {
//            final boolean fileExists = minioClient.doesObjectExist(minioConfiguration.getBucketName(), fileInfo.getPath());
//            if (fileExists) { // 文件已存在，不重复上传文件
//                return fileInfo;
//            }
//        }
//        final ObjectMetadata metadata = new ObjectMetadata();
//        metadata.setContentLength(bufferedInputStream.available());
//        // 上传文件到存储桶
//        final PutObjectResult result = minioClient.putObject(
//                minioConfiguration.getBucketName(),
//                fileInfo.getPath(),
//                bufferedInputStream,
//                metadata
//        );
//        if (log.isDebugEnabled()) {
//            log.info("versionId={}, etag={}, contentMd5={}, metadata={}"
//                    , result.getVersionId()
//                    , result.getETag()
//                    , result.getContentMd5()
//                    , JSON.toJSONString(result.getMetadata())
//            );
//        }
//        return fileInfo;
//    }

    @SneakyThrows
    @Override
    public Optional<String> readString(final FileInfo file) {
        @Cleanup final S3Object object = minioClient.getObject(minioConfiguration.getBucketName(), file.getPath());
        @Cleanup final S3ObjectInputStream inputStream = object.getObjectContent();
        @Cleanup final InputStreamReader inputStreamReader = new InputStreamReader(inputStream, UTF_8);
        return Optional.of(CharStreams.toString(inputStreamReader));
    }

    @Override
    public <T extends FileInfo, R> Optional<R> readJson(final T file, final TypeReference<R> typeReference) {
        return readString(file).map(jsonText -> JSON.parseObject(jsonText, typeReference));
    }

    @Override
    public <T extends FileInfo, R> Optional<R> readJsonObject(final T file, final Class<R> clazz) {
        return readString(file).map(jsonText -> JSON.parseObject(jsonText, clazz));
    }

    @Override
    public <T extends FileInfo, R> Optional<List<R>> readJsonArray(final T file, final Class<R> clazz) {
        return readString(file).map(jsonText -> JSON.parseArray(jsonText, clazz));
    }

    @SneakyThrows
    @Override
    public byte[] readBytes(final FileInfo file) {
        @Cleanup final S3Object object = minioClient.getObject(minioConfiguration.getBucketName(), file.getPath());
        @Cleanup final S3ObjectInputStream inputStream = object.getObjectContent();
        return ByteStreams.toByteArray(inputStream);
    }

    @SneakyThrows
    @Override
    public BufferedReader readStream(final FileInfo file) {
        @Cleanup final S3Object object = minioClient.getObject(minioConfiguration.getBucketName(), file.getPath());
        @Cleanup final S3ObjectInputStream inputStream = object.getObjectContent();
        @Cleanup final InputStreamReader inputStreamReader = new InputStreamReader(inputStream, UTF_8);
        return new BufferedReader(inputStreamReader);
    }

    @SneakyThrows
    @Override
    public InputStream readInputStream(final FileInfo file) {
        @Cleanup final S3Object object = minioClient.getObject(minioConfiguration.getBucketName(), file.getPath());
        @Cleanup final S3ObjectInputStream inputStream = object.getObjectContent();
        return inputStream;
    }

    @SneakyThrows
    @Override
    public SeekableByteChannel readChannel(final FileInfo file) {
        throw new Exception("minio 暂不支持 SeekableByteChannel");
    }

    @SneakyThrows
    @Override
    public void copy(final FileInfo file, final String absolutePath) {
        @Cleanup final FileOutputStream fileOutputStream = new FileOutputStream(absolutePath);
        ByteStreams.copy(readInputStream(file), fileOutputStream);
        FPath.of(absolutePath).chmod(644);
    }

    @SneakyThrows
    @Override
    public void copy(final FileInfo file, final OutputStream outputStream) {
        ByteStreams.copy(readInputStream(file), outputStream);
    }
}

//
///**
// * Minio 读写服务接口
// *
// * @author 谢长春 2020-11-19
// */
//@Primary
//@Slf4j
//@Service
//@RequiredArgsConstructor
//@ConditionalOnProperty(value = "spring.app.files.type", havingValue = "minio")
//public class MinioFileService implements IFileService {
//    private final MinioConfiguration minioConfiguration;
//    private final MinioClient minioClient;
//
//    @SneakyThrows
//    private void putObject(final String path, final InputStream inputStream, final boolean replace) {
//        if (log.isInfoEnabled()) {
//            log.info("上传文件路径：{}{}", minioConfiguration.getServer(), path);
//        }
//        Code.A00001.assertNonBlank(path, "必须指定存储路径");
//        if (!replace) {
//            final boolean fileExists = minioClient
//                    .listObjects(ListObjectsArgs.builder()
//                            .bucket(minioConfiguration.getBucketName())
//                            .prefix(path)
//                            .build()
//                    )
//                    .iterator()
//                    .hasNext();
//            if (fileExists) { // 文件已存在，不重复上传文件
//                return;
//            }
//        }
//        // 上传文件到存储桶
//        final ObjectWriteResponse response = minioClient.putObject(PutObjectArgs.builder()
//                .bucket(minioConfiguration.getBucketName())
//                .object(path)
//                .stream(inputStream, inputStream.available(), -1)
//                .build()
//        );
//        if (log.isDebugEnabled()) {
//            log.info("bucket={}, region={}, object={}, etag={}, versionId={}"
//                    , response.bucket()
//                    , response.region()
//                    , response.object()
//                    , response.etag()
//                    , response.versionId()
//            );
//        }
//    }
//
//    @SneakyThrows
//    @Override
//    public <T extends FileInfo> T write(final MultipartFile uploadFile, final T fileInfo, final boolean replace) {
//        putObject(fileInfo.getPath(), uploadFile.getInputStream(), replace);
//        return fileInfo;
//    }
//
//    @Override
//    public <T extends FileInfo> T write(final String content, final T fileInfo, final boolean replace) {
//        return write(content.getBytes(UTF_8), fileInfo, replace);
//    }
//
//    @SneakyThrows
//    @Override
//    public <T extends FileInfo> T write(final byte[] bytes, final T fileInfo, final boolean replace) {
//        @Cleanup final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
////        fileInfo.setName(fileName.getName());
////        byteArrayInputStream.mark(0);
////        fileInfo.setUname(fileName.toMd5Uname(byteArrayInputStream));
////        byteArrayInputStream.reset();
////        putObject(fileInfo.getPath(), byteArrayInputStream, replace);
//        putObject(fileInfo.getPath(), byteArrayInputStream, replace);
//        return fileInfo;
//    }
//
//    @SneakyThrows
//    @Override
//    public <T extends FileInfo> T write(final File file, final T fileInfo, final boolean replace) {
//        @Cleanup final FileInputStream fileInputStream = new FileInputStream(file);
//        @Cleanup final BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
////        bufferedInputStream.mark(0);
////        fileInfo.setUname(fileName.toMd5Uname(bufferedInputStream));
////        bufferedInputStream.reset();
//        putObject(fileInfo.getPath(), bufferedInputStream, replace);
//        return fileInfo;
//    }
//
//    @SneakyThrows
//    @Override
//    public <T extends FileInfo> T write(final BufferedInputStream bufferedInputStream, final T fileInfo, final boolean replace) {
////        bufferedInputStream.mark(0);
////        fileInfo.setUname(fileName.toMd5Uname(bufferedInputStream));
////        bufferedInputStream.reset();
//        putObject(fileInfo.getPath(), bufferedInputStream, replace);
//        return fileInfo;
//    }
//
//    @SneakyThrows
//    @Override
//    public Optional<String> readString(final FileInfo file) {
//        @Cleanup final InputStream inputStream = minioClient.getObject(GetObjectArgs.builder()
//                .bucket(minioConfiguration.getBucketName())
//                .object(file.getPath())
//                .build()
//        );
//        @Cleanup final InputStreamReader inputStreamReader = new InputStreamReader(inputStream, UTF_8);
//        return Optional.of(CharStreams.toString(inputStreamReader));
//    }
//
//    @Override
//    public <T extends FileInfo, R> Optional<R> readJson(final T file, final TypeReference<R> typeReference) {
//        return readString(file).map(jsonText -> JSON.parseObject(jsonText, typeReference));
//    }
//
//    @Override
//    public <T extends FileInfo, R> Optional<R> readJsonObject(final T file, final Class<R> clazz) {
//        return readString(file).map(jsonText -> JSON.parseObject(jsonText, clazz));
//    }
//
//    @Override
//    public <T extends FileInfo, R> Optional<List<R>> readJsonArray(final T file, final Class<R> clazz) {
//        return readString(file).map(jsonText -> JSON.parseArray(jsonText, clazz));
//    }
//
//    @SneakyThrows
//    @Override
//    public byte[] readBytes(final FileInfo file) {
//        @Cleanup final InputStream inputStream = minioClient.getObject(GetObjectArgs.builder()
//                .bucket(minioConfiguration.getBucketName())
//                .object(file.getUname())
//                .build()
//        );
//        return ByteStreams.toByteArray(inputStream);
//    }
//
//    @SneakyThrows
//    @Override
//    public BufferedReader readStream(final FileInfo file) {
//        @Cleanup final InputStream inputStream = minioClient.getObject(GetObjectArgs.builder()
//                .bucket(minioConfiguration.getBucketName())
//                .object(file.getUname())
//                .build()
//        );
//        @Cleanup final InputStreamReader inputStreamReader = new InputStreamReader(inputStream, UTF_8);
//        return new BufferedReader(inputStreamReader);
//    }
//}
